package com.google.appengine;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import com.ctrlspace.api.IProjectNature;
import com.ctrlspace.config.ConfigurationManager;
import com.ctrlspace.core.PluginRegistry;
import com.ctrlspace.core.compiler.IJavaCompiler;
import com.ctrlspace.core.dao.FileSystemDao;
import com.ctrlspace.core.exceptions.FileSystemException;
import com.ctrlspace.core.util.JavaUtil;
import com.ctrlspace.model.FileType;
import com.ctrlspace.model.FileUnit;
import com.ctrlspace.model.ProjectUnit;

public class AppEngineProject implements IProjectNature {

	private final String fsPath = (String) ConfigurationManager.get("PROJECT_STORAGE");
	private FileSystemDao fsDao = new FileSystemDao();

	@Override
	public ProjectUnit createProject(ProjectUnit projectUnit, Map<?, ?> options) throws Exception {
		ProjectUnit newProjectUnit = projectUnit;

		// Set up file system
		setupFileSystem(newProjectUnit);
		List<FileUnit> realFiles = getProjectFiles(projectUnit, fsPath + File.separator + newProjectUnit.getPath(), newProjectUnit.getPath(), newProjectUnit.getSourcePaths());
		newProjectUnit.setFiles(realFiles.get(0).getChildren());
		newProjectUnit.setIconClass("gaeProject");

		return newProjectUnit;
	}

	private FileUnit getProjectSourceFiles(ProjectUnit projectUnit, String path, String projectPath, String sourcePathName) {
		List<FileUnit> thisDir = new ArrayList<FileUnit>();
		File sourceDir = new File(path);

		String relativePath = getRelativePath(projectPath, sourceDir);

		// Create FileUnit for sourceDir
		FileUnit file = new FileUnit();
		file.setType(FileType.FOLDER);
		file.setPath(relativePath);

		List<FileUnit> subFiles = getFilesRecursive(projectUnit, path, projectPath);
		subFiles = subFiles.get(0).getChildren();
		file.setChildren(subFiles);
		List<FileUnit> dirs = new ArrayList<FileUnit>();

		Queue<FileUnit> queue = new LinkedList<FileUnit>();
		queue.add(file);

		while (!queue.isEmpty()) {
			FileUnit fu = queue.poll();
			if (FileType.FOLDER.equals(fu.getType())) {
				List<FileUnit> tempFiles = fu.getChildren();
				Iterator<FileUnit> it = tempFiles.iterator();
				boolean addedDirectory = false;
				while (it.hasNext()) {
					FileUnit f = it.next();
					if (FileType.FOLDER.equals(f.getType())) {
						queue.add(f);
						addedDirectory = true;
						it.remove();
					}
				}

				// Package has files or has deeper package, so show.
				if (tempFiles.size() > 0 || !addedDirectory) {
					String origPath = fu.getPath();
					origPath = origPath.substring(1);

					if (!origPath.equals(sourcePathName)) {
						int indexStart = origPath.indexOf(sourcePathName) + sourcePathName.length() + 1;
						String newName = origPath.substring(indexStart).replace(File.separator, ".");
						fu.setLabel(newName);
						fu.setProjectId(projectUnit.getProjectId());
						if (tempFiles.size() == 0) {
							fu.setType(FileType.EMPTY_PACKAGE);
						} else {
							fu.setType(FileType.PACKAGE);
						}
						fu.setChildren(tempFiles);
						dirs.add(fu);
					}
				}
			}
		}

		Collections.sort(dirs);
		thisDir.addAll(dirs);
		file.setType(FileType.PACKAGE);
		file.setFileName(sourceDir.getName());
		file.setChildren(thisDir);
		return file;
	}

	private List<FileUnit> getFilesRecursive(ProjectUnit projectUnit, String path, String projectPath) {
		List<FileUnit> thisDir = new ArrayList<FileUnit>();
		File currentDir = new File(path);

		List<FileUnit> directories = new ArrayList<FileUnit>();
		List<FileUnit> files = new ArrayList<FileUnit>();

		if (currentDir.isDirectory()) {
			String relativePath = getRelativePath(projectPath, currentDir);

			// List files in directory, in alphabetical order
			List<File> dirFiles = Arrays.asList(currentDir.listFiles());

			List<FileUnit> subDirs = new ArrayList<FileUnit>();
			List<FileUnit> subFiles = new ArrayList<FileUnit>();

			// Recursively get children of FileUnit
			List<FileUnit> children = new ArrayList<FileUnit>();
			for (File fileInDir : dirFiles) {
				if (fileInDir.isDirectory()) {
					subDirs.addAll(getFilesRecursive(projectUnit, fileInDir.getAbsolutePath(), projectPath));
				} else {
					subFiles.addAll(getFilesRecursive(projectUnit, fileInDir.getAbsolutePath(), projectPath));
				}
			}

			// For each file, create FileUnit
			FileUnit file = new FileUnit();
			file.setPath(relativePath);
			file.setType(FileType.FOLDER);
			file.setFileName(currentDir.getName());
			file.setProjectId(projectUnit.getProjectId());

			// List files and folders for packages
			children.addAll(subDirs);
			children.addAll(subFiles);

			file.setChildren(children);

			// Add to list of directories
			directories.add(file);
		} else {
			String extension = getExtension(currentDir);
			String relativePath = getRelativePath(projectPath, currentDir);

			// Create FileUnit
			FileUnit file = new FileUnit();
			file.setFileName(currentDir.getName());
			file.setType(FileType.findExtension(extension));
			file.setPath(relativePath);
			file.setProjectId(projectUnit.getProjectId());

			// Add to path
			files.add(file);
		}

		// Add all to thisDir to sort
		thisDir.addAll(0, directories);
		thisDir.addAll(directories.size(), files);

		return thisDir;
	}

	private List<FileUnit> getProjectFiles(ProjectUnit projectUnit, String path, String projectPath, List<String> sourcePaths) {
		List<FileUnit> thisDir = new ArrayList<FileUnit>();
		File currentDir = new File(path);

		List<FileUnit> directories = new ArrayList<FileUnit>();
		List<FileUnit> files = new ArrayList<FileUnit>();

		if (currentDir.isDirectory()) {
			String relativePath = getRelativePath(projectPath, currentDir);

			// Check if source path
			boolean isSourcePath = false;
			if (!relativePath.equals("")) {
				relativePath = relativePath.substring(1);
				if (sourcePaths.contains(relativePath)) {
					isSourcePath = true;
				}
			}

			if (isSourcePath) {
				directories.add(getProjectSourceFiles(projectUnit, currentDir.getAbsolutePath(), projectPath, relativePath));
			} else {
				// List files in directory, in alphabetical order
				File[] dirFiles = currentDir.listFiles();
				String[] dirFileNames = currentDir.list();
				for (int i = 0; i < dirFileNames.length; i++) {
					for (int j = i; j < dirFileNames.length; j++) {
						if (dirFileNames[i].compareToIgnoreCase(dirFileNames[j]) > 0) {
							File tempFile = dirFiles[j];
							dirFiles[j] = dirFiles[i];
							dirFiles[i] = tempFile;
						}
					}
				}

				List<FileUnit> subDirs = new ArrayList<FileUnit>();
				List<FileUnit> subFiles = new ArrayList<FileUnit>();

				// Recursively get children of FileUnit
				List<FileUnit> children = new ArrayList<FileUnit>();
				for (File fileInDir : dirFiles) {
					if (fileInDir.isDirectory()) {
						subDirs.addAll(getProjectFiles(projectUnit, fileInDir.getAbsolutePath(), projectPath, sourcePaths));
					} else {
						subFiles.addAll(getProjectFiles(projectUnit, fileInDir.getAbsolutePath(), projectPath, sourcePaths));
					}
				}

				// For each file, create FileUnit
				FileUnit file = new FileUnit();
				file.setPath(relativePath);
				file.setType(FileType.FOLDER);
				file.setFileName(currentDir.getName());
				file.setProjectId(projectUnit.getProjectId());

				// List files and folders for packages
				children.addAll(subDirs);
				children.addAll(subFiles);

				file.setChildren(children);

				// Add to list of directories
				directories.add(file);
			}
		} else {
			String extension = getExtension(currentDir);
			String relativePath = getRelativePath(projectPath, currentDir);

			// Create FileUnit
			FileUnit file = new FileUnit();
			file.setFileName(currentDir.getName());
			file.setType(FileType.findExtension(extension));
			file.setPath(relativePath);
			file.setProjectId(projectUnit.getProjectId());

			// Add to path
			files.add(file);
		}

		// Add all to thisDir to sort
		thisDir.addAll(0, directories);
		thisDir.addAll(directories.size(), files);

		return thisDir;
	}

	private String getRelativePath(String projectPath, File targetFile) {
		int startPath = targetFile.getAbsolutePath().lastIndexOf(projectPath);
		String relativePath = targetFile.getAbsolutePath().substring(startPath + projectPath.length());
		return relativePath;
	}

	private String getFullPath(String projectPath, String relativePath) {
		String fullProjectPath = fsPath + File.separator + projectPath;
		return fullProjectPath + relativePath;
	}

	private String getExtension(File currentDir) {
		// Get file extension
		int lastDot = currentDir.getName().lastIndexOf('.');

		String extension = "";
		if (lastDot != -1) {
			extension = currentDir.getName().substring(lastDot);
		} else {
			extension = "";
		}
		return extension;
	}

	private void setupFileSystem(ProjectUnit projectUnit) throws Exception {
		// Create folder structure
		String projectPath = fsPath + File.separator + projectUnit.getPath() + File.separator;

		// Create bin folder
		fsDao.createFolder(projectPath + "bin");

		// Create src folder structure
		String src = projectPath + "src" + File.separator;
		String srcMETAINF = src + "META-INF" + File.separator;
		String srcServlets = src + "servlets" + File.separator;
		fsDao.createFolder(src);
		fsDao.createFolder(srcMETAINF);
		fsDao.createFolder(srcServlets);

		// Create WebContent folder structure
		String webContent = projectPath + "WebContent" + File.separator;
		String webINF = webContent + File.separator + "WEB-INF" + File.separator;
		String webINFLib = webINF + "lib" + File.separator;
		String webINFClasses = webINF + "classes" + File.separator;

		fsDao.createFolder(webContent);
		fsDao.createFolder(webINF);
		fsDao.createFolder(webINFLib);
		fsDao.createFolder(webINFClasses);

		// Copy libraries
		File libs = new File(ConfigurationManager.get("APPENGINE_JAVA_SDK") + File.separator + "lib" + File.separator + "user");
		File libs2 = new File(ConfigurationManager.get("APPENGINE_JAVA_SDK") + File.separator + "lib" + File.separator + "shared");
		fsDao.copyDirectory(libs.getAbsolutePath(), webINFLib, false, true);
		fsDao.copyDirectory(libs2.getAbsolutePath(), webINFLib, false, true);

		// Create index.jsp
		URL indexJSPUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/index.jsp.template");
		File indexJSP = new File(indexJSPUrl.toURI().getPath());

		Map<String, String> replacements = new HashMap<String, String>();
		replacements.put("@_TITLE_@", projectUnit.getTitle());
		replacements.put("@_DESCRIPTION_@", projectUnit.getDescription());
		fsDao.createFile(webContent + "index.jsp", indexJSP, replacements);

		// Copy appengine-web.xml
		URL appengineINFUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/appengine-web.xml.template");
		File appengineXML = new File(appengineINFUrl.toURI().getPath());
		fsDao.copyFile(appengineXML, new File(webINF + "appengine-web.xml"));

		// Copy web.xml
		URL webXMLUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/web.xml.template");
		File webXML = new File(webXMLUrl.toURI().getPath());
		fsDao.copyFile(webXML, new File(webINF + "web.xml"));

		// Copy logging.properties
		URL loggingPropertiesUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/logging.properties.template");
		File loggingProperties = new File(loggingPropertiesUrl.toURI().getPath());
		fsDao.copyFile(loggingProperties, new File(webINF + "logging.properties"));

		// Copy jdoconfig.xml
		URL jdoconfigXMLUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/jdoconfig.xml.template");
		File jdoconfigXML = new File(jdoconfigXMLUrl.toURI().getPath());
		fsDao.copyFile(jdoconfigXML, new File(srcMETAINF + "jdoconfig.xml"));

		// Copy default servlet
		URL servletUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/servlet.java.template");
		File servlet = new File(servletUrl.toURI().getPath());
		fsDao.copyFile(servlet, new File(srcServlets + "HelloServlet.java"));
	}

	@Override
	public ProjectUnit getProject(ProjectUnit projectUnit) {
		List<FileUnit> realFiles = getProjectFiles(projectUnit, fsPath + File.separator + projectUnit.getPath(), projectUnit.getPath(), projectUnit.getSourcePaths());
		projectUnit.setFiles(realFiles.get(0).getChildren());
		projectUnit.setIconClass("gaeIcon");

		return projectUnit;
	}

	@Override
	public ProjectUnit openProject(ProjectUnit projectUnit) {
		List<FileUnit> realFiles = getProjectFiles(projectUnit, fsPath + File.separator + projectUnit.getPath(), projectUnit.getPath(), projectUnit.getSourcePaths());
		projectUnit.setFiles(realFiles.get(0).getChildren());
		projectUnit.setIconClass("gaeIcon");

		return projectUnit;
	}

	@Override
	public ProjectUnit closeProject(ProjectUnit projectUnit) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void register(PluginRegistry registry) {
		// TODO Auto-generated method stub

	}

	@Override
	public void addFolder(ProjectUnit projectUnit, String folderName, String path) {
		// TODO Auto-generated method stub
		String projectPath = fsPath + File.separator + projectUnit.getPath() + File.separator;
		String fileLocation;
		if (folderName.contains(".")) {
			String[] splitProjectPath = folderName.split("\\.");
			String packagePath = "";
			for (String s : splitProjectPath) {
				packagePath += s + File.separator;
			}
			fileLocation = projectPath + "src" + File.separator + packagePath;
			try {
				fsDao.createFolder(fileLocation);
			} catch (FileSystemException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			fileLocation = projectPath + path + File.separator + folderName;
			try {
				fsDao.createFolder(fileLocation);
			} catch (FileSystemException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}

	@Override
	public void addFile(ProjectUnit projectUnit, String fileName, String fileLocation) throws FileSystemException {
		// TODO Auto-generated method stub

		String projectPath = fsPath + File.separator + projectUnit.getPath() + File.separator;
		if (fileName.contains(".html"))
			try {
				URL fileUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/html.template");
				fsDao.createFile(projectPath + fileLocation + File.separator + fileName, new File(fileUrl.toURI().getPath()));
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		else if (fileName.contains(".css")) {
			try {
				URL fileUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/css.template");
				fsDao.createFile(projectPath + fileLocation + File.separator + fileName, new File(fileUrl.toURI().getPath()));
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		else if (fileName.contains(".js")) {
			try {
//				URL fileUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/css.template");
				fsDao.createFile(projectPath + fileLocation + File.separator + fileName);//, new File(fileUrl.toURI().getPath()));
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	@Override
	public void addClass(ProjectUnit projectUnit, String className, String packageName) throws URISyntaxException, FileSystemException {
		// TODO Auto-generated method stub
		String projectPath = fsPath + File.separator + projectUnit.getPath() + File.separator;
		String[] splitProjectPath = packageName.split("\\.");
		String packagePath = "";

		for (String s : splitProjectPath) {
			packagePath += s + File.separator;
		}
		String fileLocation = projectPath + "src" + File.separator + packagePath;
		try {
			fsDao.createFolder(fileLocation);
		} catch (FileSystemException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		URL classUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/class.java.template");
		Map<String, String> replacements = new HashMap<String, String>();
		replacements.put("@_PACKAGE_@", packageName);
		replacements.put("@_CLASS_NAME_@", JavaUtil.toNameSafeString(className));
		fsDao.createFile(fileLocation + className + ".java", new File(classUrl.toURI().getPath()), replacements);

	}

	@Override
	public void addInterface(ProjectUnit projectUnit, String interfaceName, String packageName) throws FileSystemException, URISyntaxException {
		// TODO Auto-generated method stub
		String projectPath = fsPath + File.separator + projectUnit.getPath() + File.separator;
		String[] splitProjectPath = packageName.split("\\.");
		String packagePath = "";

		for (String s : splitProjectPath) {
			packagePath += s + File.separator;
		}
		String fileLocation = projectPath + "src" + File.separator + packagePath;
		try {
			fsDao.createFolder(fileLocation);
		} catch (FileSystemException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		URL interfaceUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/interface.java.template");
		Map<String, String> replacements = new HashMap<String, String>();
		replacements.put("@_PACKAGE_@", packageName);
		replacements.put("@_INTERFACE_NAME_@", JavaUtil.toNameSafeString(interfaceName));
		fsDao.createFile(fileLocation + interfaceName + ".java", new File(interfaceUrl.toURI().getPath()), replacements);

	}

	@Override
	public void addEnum(ProjectUnit projectUnit, String className, String packageName) throws FileSystemException, URISyntaxException {
		// TODO Auto-generated method stub
		String projectPath = fsPath + File.separator + projectUnit.getPath() + File.separator;
		String[] splitProjectPath = packageName.split("\\.");
		String packagePath = "";

		for (String s : splitProjectPath) {
			packagePath += s + File.separator;
		}
		String fileLocation = projectPath + "src" + File.separator + packagePath;
		try {

			fsDao.createFolder(fileLocation);
		} catch (FileSystemException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		URL enumUrl = getClass().getClassLoader().getResource("com/google/appengine/resources/templates/enum.java.template");
		Map<String, String> replacements = new HashMap<String, String>();
		replacements.put("@_PACKAGE_@", packageName);
		replacements.put("@_ENUM_NAME_@", JavaUtil.toNameSafeString(className));
		fsDao.createFile(fileLocation + className + ".java", new File(enumUrl.toURI().getPath()), replacements);
	}

	@Override
	public FileUnit openFile(ProjectUnit projectUnit, String path) throws Exception {
		String filePath = getFullPath(projectUnit.getPath(), path);
		File file = new File(filePath);

		String extension = getExtension(file);

		// FileUnit
		FileUnit fileUnit = new FileUnit();
		fileUnit.setPath(path);
		fileUnit.setFileName(file.getName());
		fileUnit.setType(FileType.findExtension(extension));
		fileUnit.setProjectId(projectUnit.getProjectId());

		// Get file contents
		String contents = fsDao.openFile(filePath);
		fileUnit.setContent(contents);

		return fileUnit;
	}

	public FileUnit saveFile(ProjectUnit projectUnit, String path, String contents) throws Exception {
		String filePath = getFullPath(projectUnit.getPath(), path);
		File file = new File(filePath);

		// Save file
		fsDao.saveFile(file, contents);

		// Get File
		String extension = getExtension(file);

		// FileUnit
		FileUnit fileUnit = new FileUnit();
		fileUnit.setPath(path);
		fileUnit.setFileName(file.getName());
		fileUnit.setType(FileType.findExtension(extension));
		fileUnit.setProjectId(projectUnit.getProjectId());

		// Get file contents
		fileUnit.setContent(contents);

		return fileUnit;
	}

	public Process run(ProjectUnit projectUnit, int port) throws Exception {
		// Compile first
		String projectPath = getFullPath(projectUnit.getPath(), File.separator);
		String classes = projectPath + "WebContent" + File.separator + "WEB-INF" + File.separator + "classes" + File.separator;
		String lib = "\".;" + projectPath + "WebContent" + File.separator + "WEB-INF" + File.separator + "lib" + File.separator + "\"";
		// Get CP
//		String output = IJavaCompiler.compile(projectPath, classes, lib + "*;");
//		System.out.println(output);
		
		String jdk = "C:\\Program Files\\Java\\jdk1.6.0_16\\bin\\";
		String wawa = jdk + "java.exe -cp \"A:\\appengine-java-sdk-1.3.1\\lib\\appengine-tools-api.jar\" " + "com.google.appengine.tools.KickStart " + "com.google.appengine.tools.development.DevAppServerMain " + "--port=" + port + " " + "\"" + getFullPath(projectUnit.getPath(), File.separator + "WebContent") + "\"";
		Process p = Runtime.getRuntime().exec(wawa);

		// any error message?
		StreamGobbler errorGobbler = new StreamGobbler(p, p.getErrorStream(), "ERROR", "");

		// any output?
		StreamGobbler outputGobbler = new StreamGobbler(p, p.getInputStream(), "OUTPUT", "");

		// kick them off
		errorGobbler.start();
		outputGobbler.start();

		return p;
	}

	public void deploy(ProjectUnit projectUnit, String email, String password, String applicationId, String proxy) throws Exception {
		// put code to deploy here (appcfg)
		String jdk = "C:\\Program Files\\Java\\jdk1.6.0_16\\bin\\";

		String wawa = "";
		if (proxy.equals(null))
			wawa = jdk + "java.exe -cp " + "\"A:\\appengine-java-sdk-1.3.1\\lib\\appengine-tools-api.jar\" " + "com.google.appengine.tools.admin.AppCfg --email=" + email + "--passin update \"" + getFullPath(projectUnit.getPath(), File.separator + "WebContent") + "\"";
		else
			wawa = jdk + "java.exe -cp " + "\"A:\\appengine-java-sdk-1.3.1\\lib\\appengine-tools-api.jar\" " + "com.google.appengine.tools.admin.AppCfg --email=" + email + " --passin --proxy=" + proxy + " --proxy_https=" + proxy + " update \"" + getFullPath(projectUnit.getPath(), File.separator + "WebContent") + "\"";
		Process p = Runtime.getRuntime().exec(wawa);

		// any error message?
		StreamGobbler errorGobbler = new StreamGobbler(p, p.getErrorStream(), "ERROR", password);

		// any output?
		StreamGobbler outputGobbler = new StreamGobbler(p, p.getInputStream(), "OUTPUT", password);

		// kick them off
		errorGobbler.start();
		outputGobbler.start();
	}

	private class StreamGobbler extends Thread {
		InputStream is;
		String type;
		Process process;
		String password;

		StreamGobbler(Process process, InputStream is, String type, String password) {
			this.is = is;
			this.type = type;
			this.process = process;
			this.password = password;
		}

		public void run() {
			try {
				if (!password.equals("")) {
					BufferedOutputStream bos = (BufferedOutputStream) process.getOutputStream();
					PrintWriter pw = new PrintWriter(bos);
					pw.println(password);
					pw.flush();
				}
				InputStreamReader isr = new InputStreamReader(is);
				BufferedReader br = new BufferedReader(isr);
				String line = null;
				while ((line = br.readLine()) != null)
					System.out.println(type + ">" + line);
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
	}

}
